/*
 * Powered By XY
 * Since 2020
 */

package com.xy.biz.ts.service.impl;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import com.xy.biz.base.client.DictClient;
import com.xy.biz.base.domain.req.ListDictItemRequest;
import com.xy.biz.base.domain.resp.ListDictItemResponse;
import com.xy.biz.fd.client.DocumentClient;
import com.xy.biz.mc.client.MessagePushClient;
import com.xy.biz.mc.domain.Message;
import com.xy.biz.mc.domain.consts.MessageTypeConstant;
import com.xy.biz.mc.domain.req.MessagePushRequest;
import com.xy.biz.ts.domain.bo.*;
import com.xy.biz.ts.domain.converter.TrainConverter;
import com.xy.biz.ts.domain.converter.TrainUserConverter;
import com.xy.biz.ts.domain.enums.TrainCodeEnum;
import com.xy.biz.ts.domain.enums.TrainStatusEnum;
import com.xy.biz.ts.domain.query.*;
import com.xy.biz.ts.domain.req.*;
import com.xy.biz.ts.domain.resp.*;
import com.xy.biz.ts.manager.*;
import com.xy.biz.ts.service.PracticeRecordService;
import com.xy.biz.uc.client.UserClient;
import com.xy.biz.uc.client.UserGroupClient;
import com.xy.biz.uc.domain.dto.UserDTO;
import com.xy.biz.uc.domain.dto.UserGroupDTO;
import com.xy.biz.uc.domain.req.GetUserRequest;
import com.xy.biz.uc.domain.req.ListUserGroupByIdsRequest;
import com.xy.lang.domain.KeyValue;
import com.xy.lang.domain.Page;
import com.xy.lang.util.CalendarUtils;
import com.xy.lang.util.ResultUtils;
import com.xy.tool.context.UserContextHolder;
import com.xy.tool.gid.service.GidService;
import com.xy.tool.json.JsonUtils;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.xy.lang.domain.Result;
import com.xy.biz.ts.service.TrainService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;


/**
 *
 *
 * @author wwj
 * @date 2021-4-1
 */
@Service
public class TrainServiceImpl implements TrainService {

    @Autowired
    private UserClient userClient;
    @Autowired
    private DictClient dictClient;
    @Autowired
    private GidService gidService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private TrainManager trainManager;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private DocumentClient documentClient;
    @Autowired
    private UserGroupClient userGroupClient;
    @Autowired
    private TrainUserManager trainUserManager;
    @Autowired
    private MessagePushClient messagePushClient;
    @Autowired
    private TrainUserGroupManager trainUserGroupManager;
    @Autowired
    private PracticeRecordService practiceRecordService;
    @Autowired
    private TrainFlowHandleManager trainFlowHandleManager;
    @Autowired
    private TrainDocumentRelManager trainDocumentRelManager;
    @Autowired
    private TrainFlowServiceSupport trainFlowServiceSupport;
    @Autowired
    private ExaminationPaperManager examinationPaperManager;
    @Autowired
    private ExaminationRecordManager examinationRecordManager;
    @Autowired
    private ExaminationTrainRelManager examinationTrainRelManager;

    @Override
    public Result<?> saveTrain(TrainBO trainBO) {
        buildBO(trainBO);
        //根据处理人Id和申请原因判断是否是流程
        if (trainBO.getHandleUserId() == null || trainBO.getReason() == null) {
            TrainBO train = trainManager.saveTrain(trainBO);
            if (!ObjectUtils.isEmpty(train)) {
                saveTrainRelatedInfo(trainBO, train.getTrainId(), true);
                return Result.success();
            }
        }
        //开始新建培训的流程
        trainBO.setPhase(TrainFlowConstants.PHASE_APPLY);
        Task task = trainFlowServiceSupport.startFirstTask(trainBO, TrainFlowConstants.PROCESS_NEW_FLOW, trainBO.getHandleUserId());
        trainBO.setForeignKey(task.getProcessInstanceId());
        //TrainFlowHandleBO trainFlowHandleBO = TrainFlowHandleBO.build(trainBO, UserContextHolder.getUserId(), UserContextHolder.getUserGroupId(), TrainFlowConstants.PHASE_APPLY, TrainFlowConstants.HANDLE_RESULT_ACCEPT, false, trainBO.getReason());
        //保存培训及培训节点
        TrainBO train = trainManager.saveTrain(trainBO);
        saveTrainRelatedInfo(trainBO, train.getTrainId(), false);
        if (!ObjectUtils.isEmpty(train)) {
            //发送消息
            createMessage(trainBO.getCreateUserId(), trainBO.getHandleUserId(), TrainFlowConstants.HANDLE_RESULT_ACCEPT);
            return Result.success();
        }
        return Result.ofErrorRowsAffected();
    }

    /**
     * 第一次审核
     *
     * @param request
     */
    @Override
    public Result<?> auditTrain1(HandleTrainFlowRequest request) {
        LocalDateTime now = LocalDateTime.now();
        TrainBO train = trainManager.getTrain(request.getTrainId());
        if (Objects.equals(request.getResult(), TrainFlowConstants.HANDLE_RESULT_ACCEPT)) {
            TrainFlowHandleBO trainFlowHandleBO = TrainFlowHandleBO.build(train, UserContextHolder.getUserId(), UserContextHolder.getUserGroupId(), request.getPhase(), TrainFlowConstants.HANDLE_RESULT_ACCEPT, false, request.getOpinion());
            trainFlowHandleManager.saveTrainFlowHandle(trainFlowHandleBO);
            //train.setPhase(request.getPhase().equals(TrainFlowConstants.PHASE_AUDIT2) ? TrainFlowConstants.PHASE_AUDIT1 : TrainFlowConstants.PHASE_AUDIT2);
            trainFlowServiceSupport.gotoNextTask(train, request.getNextHandleUserId(), TrainFlowConstants.HANDLE_RESULT_ACCEPT);
        } else {
            UserDTO userDTO = ResultUtils.get(userClient.getUser(GetUserRequest.builder().userId(train.getCreateUserId()).withUserGroup(true).build()));
            TrainFlowHandleBO trainFlowHandleBO = TrainFlowHandleBO.build(train, train.getCreateUserId(), userDTO.getUserGroup().getId(), TrainFlowConstants.PHASE_APPLY, TrainFlowConstants.HANDLE_RESULT_REJECT, false, request.getOpinion());
            trainFlowHandleManager.saveTrainFlowHandle(trainFlowHandleBO);
            train.setPhase(TrainFlowConstants.PHASE_APPLY);
            trainFlowServiceSupport.gotoNextTask(train, train.getCreateUserId(), TrainFlowConstants.HANDLE_RESULT_REJECT);
        }
        train.setUpdateTime(now);
        int row = trainManager.updateTrain(train);
        if (row > 0) {
            createMessage(request.getNextHandleUserId(), train.getCreateUserId(), request.getResult());
        }
        return Result.success();
    }

    /**
     * 第二次审核
     *
     * @param request
     */
    @Override
    public Result<?> auditTrain2(HandleTrainFlowRequest request) {
        request.setPhase(TrainFlowConstants.PHASE_AUDIT2);
        return auditTrain1(request);
    }

    /**
     * 第三次审核
     *
     * @param request
     */
    @Override
    public Result<?> auditTrain3(HandleTrainFlowRequest request) {
        LocalDateTime now = LocalDateTime.now();
        TrainBO train = trainManager.getTrain(request.getTrainId());
        if (Objects.equals(request.getResult(), TrainFlowConstants.HANDLE_RESULT_ACCEPT)) {
            train.setPhase(TrainFlowConstants.PHASE_RELEASE);
            TrainFlowHandleBO trainFlowHandleBO = TrainFlowHandleBO.build(train, UserContextHolder.getUserId(), UserContextHolder.getUserGroupId(), TrainFlowConstants.PHASE_RELEASE, TrainFlowConstants.HANDLE_RESULT_ACCEPT, false, request.getOpinion());
            trainFlowHandleManager.saveTrainFlowHandle(trainFlowHandleBO);
            trainFlowServiceSupport.finishLastTask(train);
        } else {
            UserDTO userDTO = ResultUtils.get(userClient.getUser(GetUserRequest.builder().userId(train.getCreateUserId()).withUserGroup(true).build()));
            train.setPhase(TrainFlowConstants.PHASE_APPLY);
            TrainFlowHandleBO trainFlowHandleBO = TrainFlowHandleBO.build(train, train.getCreateUserId(), userDTO.getUserGroup().getId(), TrainFlowConstants.PHASE_APPLY, TrainFlowConstants.HANDLE_RESULT_REJECT, false, request.getOpinion());
            trainFlowHandleManager.saveTrainFlowHandle(trainFlowHandleBO);
            trainFlowServiceSupport.gotoNextTask(train, train.getCreateUserId(), TrainFlowConstants.HANDLE_RESULT_REJECT);
        }
        train.setUpdateTime(now);
        int row = trainManager.updateTrain(train);
        if (row > 0) {
            //改为发行状态
            trainDocumentRelManager.updateTrainDocumentRelReleaseFlag(TrainDocumentRelBO.builder().trainId(train.getTrainId()).releaseFlag(true).build());
            trainUserManager.updateTrainUserReleaseFlag(TrainUserBO.builder().trainId(train.getTrainId()).releaseFlag(true).build());
            trainUserGroupManager.updateTrainUserGroupReleaseFlag(TrainUserGroupBO.builder().trainId(train.getTrainId()).releaseFlag(true).build());
            createMessage(train.getCreateUserId(), train.getCreateUserId(), request.getResult());
        }
        return Result.success();
    }

    /**
     * 流程终止
     *
     * @param request
     */
    @Override
    public Result<?> finishTrainFlow(GetTrainRequest request) {
        TrainBO train = trainManager.getTrain(request.getTrainId());
        if (train != null) {
            trainFlowServiceSupport.finish(train);
            return Result.success();
        }
        return Result.fail();
    }

    private void createMessage(Long NextHandleUserId, Long createUserId, int result) {
        //发送消息
        //通过
        if (Objects.equals(result, TrainFlowConstants.HANDLE_RESULT_ACCEPT)) {
            //发送消息
            //定义消息内容
            Message message = Message.builder()
                    .type(MessageTypeConstant.TYPE_FLOW)
                    .data(1)
                    .remark("新增培训流程-审核")
                    .path(MessageTypeConstant.FLOW_LIST)
                    .build();
            sendMessage(message, NextHandleUserId);
        } else {//拒绝通过
            //定义消息内容
            Message message = Message.builder()
                    .type(MessageTypeConstant.TYPE_FLOW)
                    .data(1)
                    .remark("新增培训流程-审核不通过")
                    .path(MessageTypeConstant.FLOW_LIST)
                    .build();
            sendMessage(message, createUserId);
        }
    }

    private void saveTrainRelatedInfo(TrainBO trainBO, Long trainId, Boolean releaseFlag) {
        //团队转化为人员进行标注发放
        List<Long> participateUserId = trainBO.getParticipateUserId();
        List<Long> participateGroupId = trainBO.getParticipateGroupId();
        ArrayList<TrainUserBO> trainUserBOS = new ArrayList<>();
        if (!CollectionUtils.isEmpty(participateGroupId)) {
            //查找团队下的成员
            Set<Long> collect = ResultUtils.get(userClient.listUserByUserGroup(ListUserGroupByIdsRequest.builder().groupIds(participateGroupId).build())).stream().map(UserDTO::getUserId).collect(Collectors.toSet());
            if (CollectionUtils.isEmpty(participateUserId)) {
                participateUserId = new ArrayList();
            }
            participateUserId.addAll(collect);
            Map<Long, List<UserDTO>> map = ResultUtils.get(userClient.listUserById(participateUserId)).stream().collect(Collectors.groupingBy(u -> u.getUserId()));
            participateUserId.forEach(uid -> {
                TrainUserBO trainUserBO = TrainUserBO.builder().trainId(trainId)
                        .userId(uid)
                        .userGroupId(map.get(uid).get(0).getUserGroup().getId())
                        .status(0)
                        .tenantId(0L)
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .releaseFlag(releaseFlag)
                        .fromGroupFlag(true)
                        .deleteFlag(false)
                        .build();
                trainUserBOS.add(trainUserBO);
            });
        }
        //发放人员
        if (!CollectionUtils.isEmpty(participateUserId)) {
            Map<Long, List<UserDTO>> collect = ResultUtils.get(userClient.listUserById(participateUserId)).stream().collect(Collectors.groupingBy(u -> u.getUserId()));
            participateUserId.forEach(uid -> {
                TrainUserBO trainUserBO = TrainUserBO.builder().trainId(trainId)
                        .userId(uid)
                        .userGroupId(collect.get(uid).get(0).getUserGroup().getId())
                        .status(0)
                        .tenantId(0L)
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .releaseFlag(releaseFlag)
                        .fromGroupFlag(false)
                        .deleteFlag(false)
                        .build();
                trainUserBOS.add(trainUserBO);
            });
            trainUserManager.saveAllTrainUser(trainUserBOS);
        }
        //发放部门
        if (!CollectionUtils.isEmpty(participateGroupId)) {
            ArrayList<TrainUserGroupBO> trainUserGroupBOS = new ArrayList<>();
            participateGroupId.forEach(gid -> {
                TrainUserGroupBO trainUserGroupBO = TrainUserGroupBO.builder().trainId(trainId)
                        .userGroupId(gid)
                        .checkUserCount(0)
                        .tenantId(0L)
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .releaseFlag(releaseFlag)
                        .deleteFlag(false).build();
                trainUserGroupBOS.add(trainUserGroupBO);
            });
            trainUserGroupManager.saveAllTrainUserGroup(trainUserGroupBOS);
        }
        //文档培训
        List<Long> documentIds = trainBO.getDocumentIds();
        if (!CollectionUtils.isEmpty(documentIds)) {
            List<TrainDocumentRelBO> trainDocumentRelBOS = new ArrayList<>();
            documentIds.forEach(id ->
                    trainDocumentRelBOS.add(TrainDocumentRelBO.builder()
                            .documentId(id)
                            .tenantId(0L)
                            .trainId(trainId)
                            .releaseFlag(releaseFlag)
                            .deleteFlag(false)
                            .build())
            );
            trainDocumentRelManager.saveAllTrainDocumentRel(trainDocumentRelBOS);
        }
    }

    private void buildBO(TrainBO trainBO) {
        trainBO.setTrainId(gidService.generate());
        trainBO.setDeleteFlag(false);
        trainBO.setDocumentFlowId(trainBO.getDocumentFlowId());
        trainBO.setCreateTime(LocalDateTime.now());
        trainBO.setCreateUserId(UserContextHolder.getUserId());
        trainBO.setAuditUserId(trainBO.getHandleUserId());
        if (!trainBO.getCertificateFlag()) {
            trainBO.setCertificateName(null);
        }
        trainBO.setStatus(0);
        trainBO.setTenantId(UserContextHolder.getTenantId());
        trainBO.setUpdateTime(LocalDateTime.now());
        trainBO.setCreateUserGroupId(UserContextHolder.getUserGroupId());
    }

    /**
     * 查找多个
     *
     * @param query 查找条件
     */
    @Override
    public Page<GetTrainResponse> searchTrainFlowTodo(TrainFlowHandleQuery query) {
        List<TrainBO> trainBOS = new ArrayList<>();
        List<String> flow = new ArrayList<>();
        //查询培训流程
        flow.add(TrainFlowConstants.PROCESS_NEW_FLOW);
        long total = taskService.createTaskQuery()
                .taskAssignee(UserContextHolder.getUserId().toString())
                .processDefinitionKeyIn(flow)
                .count();
        if (total > 0) {
            List<Task> taskList = taskService.createTaskQuery()
                    .taskAssignee(UserContextHolder.getUserId().toString())
                    .processDefinitionKeyIn(flow)
                    .orderByTaskCreateTime()
                    .desc()
                    .listPage(query.getOffset(), query.getRows());

            if (!CollectionUtils.isEmpty(taskList)) {
                Set<String> processInstanceIdSet = taskList.stream().map(Task::getProcessInstanceId).collect(Collectors.toSet());
                List<ProcessInstance> processInstanceList = runtimeService.createProcessInstanceQuery().processInstanceIds(processInstanceIdSet).list();
                Set<Long> trainFlowIdSet = processInstanceList.stream().map(e -> Long.parseLong(e.getBusinessKey())).collect(Collectors.toSet());
                trainBOS = trainManager.listTrainById(trainFlowIdSet);
            }
        }
        trainBOS = trainBOS.stream().peek(t -> {
            t.setAccessTimeString(CalendarUtils.getDeadlineString(t.getUpdateTime(), LocalDateTime.now()));
            t.setPhaseName(TrainFlowConstants.getPhaseName(t.getType(), t.getPhase()));
            t.setMode(dictClient.getValue(t.getMode()));
            t.setType(dictClient.getValue(t.getType()));
            t.setAssess(dictClient.getValue(t.getAssess()));
            t.setPhaseNum(TrainFlowConstants.getPhaseNum(t.getPhase()));
            t.setTrainClassify(dictClient.getValue(t.getTrainClassify()));
        }).collect(Collectors.toList());
        userClient.buildByUser(trainBOS,
                trainBOS.stream().map(TrainBO::getCreateUserId).collect(Collectors.toSet()),
                TrainBO::getCreateUserId,
                (t, user) -> t.setCreateUserName(user.getUserName())
        );
        userClient.buildByUser(trainBOS,
                trainBOS.stream().map(TrainBO::getTrainerUserId).collect(Collectors.toSet()),
                TrainBO::getCreateUserId,
                (t, user) -> t.setTrainerUserName(user.getUserName())
        );
        List<GetTrainResponse> getTrainResponses = TrainConverter.toResponse(trainBOS);
        Collections.sort(getTrainResponses, Comparator.comparing(GetTrainResponse::getUpdateTime).reversed());
        return new Page(getTrainResponses, (int) total);
    }


    @Override
    public Result<?> saveAllTrain(Collection<TrainBO> collection) {
        int affectedRows = trainManager.saveAllTrain(collection);
        if (affectedRows > 0) {
            return Result.success();
        }
        return Result.ofErrorRowsAffected();
    }

    @Transactional
    @Override
    public Result<?> updateTrain(TrainBO trainBO) {
        buildUpdateTrainBO(trainBO);
        int affectedRows = trainManager.updateTrain(trainBO);
        if (affectedRows > 0) {
            trainUserManager.deleteTrainUserByTrainId(trainBO.getTrainId());
            trainUserGroupManager.deleteTrainUserGroupByTrainId(trainBO.getTrainId());
            trainDocumentRelManager.deleteTrainDocumentRelByTrainId(trainBO.getTrainId());
            saveTrainRelatedInfo(trainBO, trainBO.getTrainId(), true);
            return Result.success();
        }
        return Result.ofErrorRowsAffected();
    }

    /**
     * 更新培训状态
     *
     */
    @Override
    public Result<?> changeStatus() {
        List<TrainBO> trainBOS = trainManager.listTrainId(TrainQuery.max().build());
        Set<Long> startIds = new HashSet<>();
        Set<Long> undoneIds = new HashSet<>();
        trainBOS.forEach(t -> {
            if (t.getStatus().equals(TrainStatusEnum.NOT_TIME_YET.getCode()) && LocalDateTime.now().isAfter(t.getStartTime())) {
                startIds.add(t.getTrainId());
            }
            if (t.getStatus().equals(2)) {
                undoneIds.add(t.getTrainId());
            }
        });
        Map<Integer, Set<Long>> trainIdMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(startIds)) {
            trainIdMap.put(TrainStatusEnum.UNFINISHED.getCode(), startIds);
        }
        if (!CollectionUtils.isEmpty(undoneIds)) {
            Map<Long, List<TrainUserBO>> trainUserMap = trainUserManager.listTrainUser(TrainUserQuery.builder().trainIdS(undoneIds).build()).stream().collect(Collectors.groupingBy(TrainUserBO::getTrainId));
            //考试记录
            Map<Long, List<ExaminationRecordBO>> examinationMap = examinationRecordManager.listExaminationRecord(ExaminationRecordQuery.max().trainIds(undoneIds).build()).stream().collect(Collectors.groupingBy(ExaminationRecordBO::getTrainId));
            Set<Long> finishedIds = new HashSet<>();
            for (Long key : trainUserMap.keySet()) {
                if (!examinationMap.containsKey(key)) {
                    continue;
                }
                Set<Long> userIds = examinationMap.get(key).stream().map(ExaminationRecordBO::getExamineeUserId).collect(Collectors.toSet());
                Set<Long> userIds1 = trainUserMap.get(key).stream().map(TrainUserBO::getUserId).collect(Collectors.toSet());
                if (userIds1.containsAll(userIds)) {
                    finishedIds.add(key);
                }
            }
            if (!CollectionUtils.isEmpty(finishedIds)) {
                trainIdMap.put(TrainStatusEnum.FINISHED.getCode(), finishedIds);
            }
        }
        int row = 0;
        if (!CollectionUtils.isEmpty(trainIdMap)) {
            row = trainManager.changeStatus(trainIdMap);
        }
        return Result.success(row);
    }

    private void buildUpdateTrainBO(TrainBO trainBO) {
        if (!trainBO.getCertificateFlag()) {
            trainBO.setCertificateFlag(null);
        }
        trainBO.setUpdateTime(LocalDateTime.now());
    }

    @Override
    public Result<?> deleteTrain(Long trainId) {
        int affectedRows = trainManager.deleteTrain(trainId);
        if (affectedRows > 0) {
            return Result.success();
        }
        return Result.ofErrorRowsAffected();
    }

    @Override
    public TrainBO getTrain(Long trainId) {
        TrainBO trainBO = trainManager.getTrain(trainId);
        if (Objects.isNull(trainBO)) {
            return null;
        }

        List<TrainDocumentRelBO> trainDocumentRelBOS = trainDocumentRelManager.listTrainDocumentRel(TrainDocumentRelQuery.max().trainId(trainId).build());
        if (!CollectionUtils.isEmpty(trainDocumentRelBOS)) {
            List<Long> documentIds = trainDocumentRelBOS.stream().map(TrainDocumentRelBO::getDocumentId).collect(Collectors.toList());
            documentClient.buildByDocument(trainDocumentRelBOS,
                    documentIds,
                    TrainDocumentRelBO::getDocumentId,
                    (b, r) -> b.setDocumentName(r.getDocumentName()));
            trainBO.setTrainDocumentRelBOS(trainDocumentRelBOS);
        }

        List<TrainFlowHandleBO> trainFlowHandleBOS = trainFlowHandleManager.listTrainFlowHandle(TrainFlowHandleQuery.max().trainId(trainId).build());

        Map<Long, String> userNameMap = new HashMap<>();
        List<TrainUserBO> trainUserBOS = trainUserManager.listTrainUser(TrainUserQuery.max().trainId(trainId).withPracticeRecords(false).withExaminationRecords(false).withLearnRecords(false).build());
        if (!CollectionUtils.isEmpty(trainUserBOS)) {
            trainUserBOS = trainUserBOS.stream().filter(distinctByKey(TrainUserBO::getUserId)).collect(Collectors.toList());
            List<Long> userIdList = trainUserBOS.stream().map(TrainUserBO::getUserId).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(trainFlowHandleBOS)) {
                userIdList.addAll(trainFlowHandleBOS.stream().map(TrainFlowHandleBO::getHandleUserId).collect(Collectors.toSet()));
            }
            if (Objects.nonNull(trainBO.getAuditUserId())) {
                userIdList.add(trainBO.getAuditUserId());
            }
            userIdList.add(trainBO.getTrainerUserId());
            Objects.requireNonNull(userIdList);
            userNameMap.putAll(ResultUtils.get(userClient.listUserById(userIdList)).stream().collect(Collectors.toMap(UserDTO::getUserId, UserDTO::getUserName, (e1, e2) -> e2)));
            trainUserBOS.forEach(e -> {
                e.setUserName(userNameMap.get(e.getUserId()));
            });
            trainBO.setTrainUserBOS(trainUserBOS);
            if (Objects.nonNull(trainBO.getAuditUserId())) {
                trainBO.setAuditUserName(userNameMap.get(trainBO.getAuditUserId()));
            }
            trainBO.setTrainerUserName(userNameMap.get(trainBO.getTrainerUserId()));
        }

        Map<Long, String> userGroupNameMap = new HashMap<>();
        List<TrainUserGroupBO> trainUserGroupBOS = trainUserGroupManager.listTrainUserGroup(TrainUserGroupQuery.max().trainId(trainId).build());
        if (!CollectionUtils.isEmpty(trainUserGroupBOS)) {
            List<Long> userGroupIdList = trainUserGroupBOS.stream().map(TrainUserGroupBO::getUserGroupId).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(trainFlowHandleBOS)) {
                userGroupIdList.addAll(trainFlowHandleBOS.stream().map(TrainFlowHandleBO::getHandleUserGroupId).collect(Collectors.toSet()));
            }
            userGroupNameMap.putAll(ResultUtils.get(userGroupClient.listUserGroupByIds(userGroupIdList)).stream().collect(Collectors.toMap(UserGroupDTO::getUserGroupId, UserGroupDTO::getUserGroupName, (e1, e2) -> e2)));
            if (!CollectionUtils.isEmpty(userGroupNameMap)) {
                trainUserGroupBOS.forEach(e -> e.setUserGroupName(userGroupNameMap.get(e.getUserGroupId())));
            }
            trainBO.setTrainUserGroupBOS(trainUserGroupBOS);
        }

        if (!CollectionUtils.isEmpty(trainFlowHandleBOS)) {
            if (!CollectionUtils.isEmpty(userGroupNameMap)) {
                trainFlowHandleBOS.forEach(e -> {
                    e.setHandleUserName(userNameMap.get(e.getHandleUserId()));
                    e.setHandleUserGroupName(userGroupNameMap.get(e.getHandleUserGroupId()));
                });
            } else {
                trainFlowHandleBOS.forEach(e -> e.setHandleUserName(userNameMap.get(e.getHandleUserId())));
            }
            trainBO.setTrainFlowHandleList(trainFlowHandleBOS);
        }

        return trainBO;
    }

    private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    @Override
    public List<TrainBO> listTrainById(Collection<Long> collection) {
        return trainManager.listTrainById(collection);
    }

    /**
     * 查找多个
     *
     * @param request 标识集合
     */
    @Override
    public List<GetTrainResponse> listTrainByFlow(ListTrainRequest request) {
        TrainQuery trainQuery = TrainConverter.toQuery(request);
        List<TrainBO> trainBOS = trainManager.listTrain(trainQuery);
        if (CollectionUtils.isEmpty(trainBOS)) {
            return null;
        }
        List<GetTrainResponse> getTrainResponses = TrainConverter.toResponse(trainBOS);
        Set<Long> userIds = new HashSet<>();
        getTrainResponses.stream().peek(t -> {
            if (!CollectionUtils.isEmpty(t.getTrainUserResponses())) {
                userIds.addAll(t.getTrainUserResponses().stream().map(TrainUserResponse::getUserId).collect(Collectors.toSet()));
            }
            if (!CollectionUtils.isEmpty(t.getTrainUserGroupResponses())) {
                t.getTrainUserGroupResponses().stream().peek(t2 -> {
                    if (!CollectionUtils.isEmpty(t2.getTrainUserResponses())) {
                        userIds.addAll(t2.getTrainUserResponses().stream().map(TrainUserResponse::getUserId).collect(Collectors.toSet()));
                    }
                }).collect(Collectors.toList());
            }
        }).collect(Collectors.toSet());
        Map<Long, List<UserDTO>> collect = ResultUtils.get(userClient.listUserById(userIds)).stream().collect(Collectors.groupingBy(UserDTO::getUserId));
        getTrainResponses.stream().peek(t -> {
            if (!CollectionUtils.isEmpty(t.getTrainUserResponses())) {
                t.getTrainUserResponses().stream().peek(t1 -> t1.setUserName(collect.get(t1.getUserId()).get(0).getUserName())).collect(Collectors.toSet());
            }
            if (!CollectionUtils.isEmpty(t.getTrainUserGroupResponses())) {
                t.getTrainUserGroupResponses().stream().peek(t2 -> {
                    if (!CollectionUtils.isEmpty(t2.getTrainUserResponses())) {
                        t2.getTrainUserResponses().stream().peek(t3 -> t3.setUserName(collect.get(t3.getUserId()).get(0).getUserName())).collect(Collectors.toSet());
                    }
                }).collect(Collectors.toList());
            }
        }).collect(Collectors.toSet());
        return getTrainResponses;
    }

    @Override
    public List<GetTrainResponse> listParticipantTrain(TrainQuery query) {
        Long userId = UserContextHolder.getUserId();
        List<TrainUserBO> trainUserBOS = trainUserManager.listTrainUser(TrainUserQuery.max().releaseFlag(true).fromGroupFlag(false).withLearnRecords(false).withExaminationRecords(false).withPracticeRecords(false).userId(userId).build());
        if (CollectionUtils.isEmpty(trainUserBOS)) {
            return null;
        }
        Set<Long> userTrainIds = trainUserBOS.stream().map(TrainUserBO::getTrainId).collect(Collectors.toSet());
        Long userGroupId = UserContextHolder.getUserGroupId();
        List<TrainUserGroupBO> trainUserGroupBOS = trainUserGroupManager.listTrainUserGroup(TrainUserGroupQuery.max().releaseFlag(true).userGroupId(userGroupId).build());
        Set<Long> userGroupTrainIds = trainUserGroupBOS.stream().map(TrainUserGroupBO::getTrainId).collect(Collectors.toSet());
        userTrainIds.addAll(userGroupTrainIds);
        query.setTrainIdS(userTrainIds);
        List<TrainBO> trainBOS = trainManager.listTrain(query);
        buildDocumentName(trainBOS, userTrainIds);
        List<GetTrainResponse> getTrainResponses = TrainConverter.toResponse(trainBOS);
        buildTrainInfo(getTrainResponses);
        userClient.buildByUser(getTrainResponses,
                getTrainResponses.stream().map(GetTrainResponse::getCreateUserId).collect(Collectors.toSet()),
                GetTrainResponse::getCreateUserId,
                (t, userDTO) -> t.setCreateUserName(userDTO.getUserName()));
        getTrainResponses = getTrainResponses.stream().peek(t -> {
            t.setId(t.getTrainId());
            t.setLabel(t.getName());
            t.setModeName(dictClient.getValue(t.getMode()));
            t.setAssessName(dictClient.getValue(t.getAssess()));
        }).collect(Collectors.toList());
        return getTrainResponses;
    }

//    @Override
//    public GetTrainByClassifyResponse listMyStartTrain() {
//        Long userId = UserContextHolder.getUserId();
//        List<TrainBO> trainBOS = trainManager.listTrain(TrainQuery.max().createUserId(userId).withUserGroup(true).withUser(true).build());
//        Set<Long> trainIds = trainBOS.stream().map(TrainBO::getTrainId).collect(Collectors.toSet());
//        buildDocumentName(trainBOS, trainIds);
//        List<GetTrainResponse> getTrainResponses = TrainConverter.toResponse(trainBOS);
//        buildTrainInfo(getTrainResponses);
//        userClient.buildByUser(getTrainResponses,
//                getTrainResponses.stream().map(GetTrainResponse::getCreateUserId).collect(Collectors.toSet()),
//                GetTrainResponse::getCreateUserId,
//                (t, userDTO) -> t.setCreateUserName(userDTO.getUserName()));
//        return buildResponse(getTrainResponses);
//    }

    @Override
    public List<KeyValue> listTrainClassify() {
        List<ListDictItemResponse> dictItemResponses = ResultUtils.get(dictClient.listDictItem(ListDictItemRequest.builder().dictGroupCode(TrainCodeEnum.TRAIN.getCode()).build()));
        return TrainConverter.toKeyValueList(dictItemResponses);
    }

    /**
     * 获取分类下的培训
     * @param query
     */
    @Override
    public List<GetTrainResponse> listTrainByClassify(TrainQuery query) {
        List<GetTrainResponse> getTrainResponses = TrainConverter.toResponse(listTrain(query));
        getTrainResponses = getTrainResponses.stream().peek(t -> {
            t.setId(t.getTrainId());
            t.setLabel(t.getName());
            t.setModeName(dictClient.getValue(t.getMode()));
            t.setTrainFormName(dictClient.getValue(t.getTrainForm()));
            t.setExpenseCategoryName(dictClient.getValue(t.getExpenseCategory()));
            t.setAssessName(dictClient.getValue(t.getAssess()));
        }).collect(Collectors.toList());
        userClient.buildByUser(getTrainResponses,
                getTrainResponses.stream().map(GetTrainResponse::getTrainerUserId).collect(Collectors.toSet()),
                GetTrainResponse::getTrainerUserId,
                (t, u) -> t.setTrainerUserName(u.getUserName()));
        getTrainResponses.sort((m1, m2) -> m2.getCreateTime().compareTo(m1.getCreateTime()));
        userGroupClient.buildByUserGroup(getTrainResponses,
                getTrainResponses.stream().map(GetTrainResponse::getCreateUserGroupId).collect(Collectors.toSet()),
                GetTrainResponse::getCreateUserGroupId,
                (t, u) -> t.setCreateUserGroupName(u.getUserGroupName()));
        return getTrainResponses;
    }

    @Override
    public List<TrainUserBO> listRecordByTrain(TrainUserQuery query) {
        List<TrainUserBO> trainUserBOList = trainUserManager.listTrainUserFilterByUserId(query);
        if (CollectionUtils.isEmpty(trainUserBOList)) {
            return Collections.emptyList();
        }
        if (!query.getWithPracticeRecords()) {
            ExaminationTrainRelBO examinationTrainRel = examinationTrainRelManager.getExaminationTrainRelByTrainId(query.getTrainId());
            if (Objects.nonNull(examinationTrainRel)) {
                ExaminationPaperBO examinationPaper = examinationPaperManager.getExaminationPaper(examinationTrainRel.getExaminationPaperId());
                if (Objects.nonNull(examinationPaper)) {
                    trainUserBOList.forEach(e -> {
                        e.setExaminationPaperId(examinationPaper.getExaminationPaperId());
                        e.setExaminationTime(examinationTrainRel.getCreateTime());
                        e.setExaminationPaperName(examinationPaper.getExaminationPaperName());
                    });
                }
            }
        }
        userClient.buildByUser(trainUserBOList,
                trainUserBOList.stream().map(TrainUserBO::getUserId).collect(Collectors.toSet()),
                TrainUserBO::getUserId,
                (t, u) -> t.setUserName(u.getUserName()));
        userGroupClient.buildByUserGroup(trainUserBOList,
                trainUserBOList.stream().map(TrainUserBO::getUserGroupId).collect(Collectors.toSet()),
                TrainUserBO::getUserGroupId,
                (t, u) -> t.setUserGroupName(u.getUserGroupName()));

        return trainUserBOList;
    }

    /**
     * 我培训的
     * @param query
     */
    @Override
    public List<GetTrainResponse> listMyTraining(TrainQuery query) {
        query.setTrainerUserId(UserContextHolder.getUserId());
        return listTrainByClassify(query);
    }

    /**
     * 获取培训发行人
     *
     * @param query
     */
    @Override
    public List<GetTrainUserResponse> listReleaseUserByTrain(TrainUserQuery query) {
        List<TrainUserBO> trainUserBOS = trainUserManager.listTrainUserFilterByUserId(query);
        Set<Long> userIds = trainUserBOS.stream().map(TrainUserBO::getUserId).collect(Collectors.toSet());
        Set<Long> userGroupIds = trainUserBOS.stream().map(TrainUserBO::getUserGroupId).collect(Collectors.toSet());
        userClient.buildByUser(trainUserBOS,
                userIds,
                TrainUserBO::getUserId,
                (t, u) -> t.setUserName(u.getUserName()));
        userGroupClient.buildByUserGroup(trainUserBOS,
                userGroupIds,
                TrainUserBO::getUserGroupId,
                (t, u) -> t.setUserGroupName(u.getUserGroupName()));
        return TrainUserConverter.toResponse(trainUserBOS);
    }

    /**
     * 获取培训发行人
     * @param request
     */
    @Override
    public Result<?> remindTest(SendToUserRequest request) {
        if (CollectionUtils.isEmpty(request.getUserIds())) {
            return Result.fail("发送人id不能为空！！");
        }
        //定义消息内容
        Message message = Message.builder()
                .type(MessageTypeConstant.TYPE_FLOW)
                .data(0)
                .remark("请尽快完成 " + request.getTrainName() + " 的自学任务！")
                .path(MessageTypeConstant.DOCUMENT_RELEASE_CHECK)
                .build();
        request.getUserIds().forEach(u -> sendMessage(message, u));
        return Result.success();
    }

    public GetTrainByClassifyResponse buildResponse(List<GetTrainResponse> getTrainResponses) {
        GetTrainByClassifyResponse getTrainByClassifyResponse = new GetTrainByClassifyResponse();
        List<GetTrainByClassifyResponse> list = new ArrayList<>();
        GetTrainByClassifyResponse year = new GetTrainByClassifyResponse();
        year.setTrainId(1L);
        year.setName(dictClient.getValue(TrainCodeEnum.YEAR.getCode()));
        List<GetTrainResponse> collect = getTrainResponses.stream().filter(t -> t.getTrainClassify().equals(TrainCodeEnum.YEAR.getCode())).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(collect)) {
            collect.sort((c1, c2) -> c2.getUpdateTime().compareTo(c1.getUpdateTime()));
        }
        year.setData(collect);

        GetTrainByClassifyResponse common = new GetTrainByClassifyResponse();
        common.setTrainId(2L);
        common.setName(dictClient.getValue(TrainCodeEnum.COMMON.getCode()));
        List<GetTrainResponse> collect1 = getTrainResponses.stream().filter(t -> t.getTrainClassify().equals(TrainCodeEnum.COMMON.getCode())).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(collect)) {
            collect1.sort((c1, c2) -> c2.getUpdateTime().compareTo(c1.getUpdateTime()));
        }
        common.setData(getTrainResponses.stream().filter(t -> t.getTrainClassify().equals(TrainCodeEnum.COMMON.getCode())).collect(Collectors.toList()));

        list.add(year);
        list.add(common);
        getTrainByClassifyResponse.setTrainByClassifyResponse(list);
        return getTrainByClassifyResponse;
    }

    private void buildDocumentName(List<TrainBO> trainBOS, Set<Long> trainIds) {
        if (!CollectionUtils.isEmpty(trainBOS) && !CollectionUtils.isEmpty(trainIds)) {
            List<TrainDocumentRelBO> trainDocumentRelBOS = trainDocumentRelManager.listTrainDocumentRel(TrainDocumentRelQuery.max().trainIds(trainIds).build());
            Set<Long> documentIds = trainDocumentRelBOS.stream().map(TrainDocumentRelBO::getDocumentId).collect(Collectors.toSet());
            documentClient.buildByDocument(
                    trainDocumentRelBOS,
                    documentIds,
                    TrainDocumentRelBO::getDocumentId,
                    (tdr, d) -> tdr.setDocumentName(d.getDocumentName()));
            Map<Long, List<TrainDocumentRelBO>> trainDocumentMap = trainDocumentRelBOS.stream().collect(Collectors.groupingBy(TrainDocumentRelBO::getTrainId));
            trainBOS.forEach(tb -> tb.setTrainDocumentRelBOS(trainDocumentMap.get(tb.getTrainId())));
            Set<Long> trainUserIdSet = trainBOS.stream().map(TrainBO::getTrainerUserId).collect(Collectors.toSet());
            userClient.buildByUser(trainBOS, trainUserIdSet, TrainBO::getTrainerUserId,
                    (tb, ut) -> tb.setTrainerUserName(ut.getUserName()));
        }
    }

    private void buildTrainInfo(List<GetTrainResponse> getTrainResponses) {
        getTrainResponses.stream().forEach(t -> {
            t.setTypeName(dictClient.getValue(t.getType()));
            t.setAssessName(dictClient.getValue(t.getAssess()));
            t.setModeName(dictClient.getValue(t.getMode()));
            t.setTrainClassifyName(dictClient.getValue(t.getTrainClassify()));
        });
    }

    @Override
    public List<TrainBO> listTrain(TrainQuery query) {
        return trainManager.listTrain(query);
    }

    /**
     * 查找多个
     *
     * @param query
     */
    @Override
    public List<TrainBO> listPracticalOperation(TrainQuery query) {
        Set<Long> trainIdSet = trainUserManager.listTrainUser(
                        TrainUserQuery.max().userId(UserContextHolder.getUserId()).build())
                .stream().map(TrainUserBO::getTrainId).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(trainIdSet)) {
            return Collections.emptyList();
        }
        query.setOffset(0);
        query.setRows(Integer.MAX_VALUE);
        query.setTrainIdS(trainIdSet);
        List<TrainBO> trainBOList = trainManager.listTrain(query);
        if (!CollectionUtils.isEmpty(trainBOList)) {
            Map<Long, List<PracticeRecordBO>> practiceRecordMap = practiceRecordService.listPracticeRecord(
                            PracticeRecordQuery.max().practiceUserId(UserContextHolder.getUserId()).trainIds(trainIdSet).build())
                    .stream().collect(Collectors.groupingBy(PracticeRecordBO::getTrainId));
            if (!CollectionUtils.isEmpty(practiceRecordMap)) {
                trainBOList.forEach(e -> {
                    e.setPracticeRecordList(practiceRecordMap.get(e.getTrainId()));
                });
            }
        }
        return trainBOList;
    }

    @Override
    public int countTrain(TrainQuery query) {
        return trainManager.countTrain(query);
    }

    /**
     * 发送信息
     * @param message
     * @param toId
     */
    protected void sendMessage(Message message, Long toId) {
        messagePushClient.send(MessagePushRequest.builder()
                .data(JsonUtils.toJson(message).getBytes())
                .dataType(0)
                .expiredSeconds(0L)
                .toId(toId)
                .offlineFlag(true).build());
    }
}
